package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/**
 * @Description 消息发送确认
 * <p>
 * ConfirmCallback  只确认消息是否正确到达 Exchange 中
 * ReturnCallback   消息没有正确到达队列时触发回调，如果正确到达队列不执行
 * <p>
 * 1. 如果消息没有到exchange,则confirm回调,ack=false
 * 2. 如果消息到达exchange,则confirm回调,ack=true
 * 3. exchange到queue成功,则不回调return
 * 4. exchange到queue失败,则回调return
 */
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;


    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);            //指定 ConfirmCallback
        rabbitTemplate.setReturnCallback(this);             //指定 ReturnCallback
    }

    //交换机的应答消息接收方法  交换机接收到消息应答   交换机接收不到消息应答
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //不管交换机接收消息与否  都进行应答
        if (ack) {
            //交换机接收到消息了
            log.info("消息发送成功：" + JSON.toJSONString(correlationData));
        } else {
            //交换机没有接收到消息  后期是要处理
            log.info("消息发送失败：" + cause + " 数据：" + JSON.toJSONString(correlationData));

            //调用下面的重新发送消息的方法
            GmallCorrelationData gmallCorrelationData = (GmallCorrelationData) correlationData;
            this.addRetry(gmallCorrelationData);


        }
    }

    //重新发送消息
    private void addRetry(GmallCorrelationData gmallCorrelationData) {
        //1：5分钟之后再发    交换机、routingKey、消息
        //2:要求只能重发3次   如果超出3次 记录日志保存
        int retryCount = gmallCorrelationData.getRetryCount();
        if(retryCount < MqConst.RETRY_COUNT){
            //可以重试
            gmallCorrelationData.setRetryCount(++retryCount);
            //更新缓存中重试的次数
            redisTemplate.opsForValue().set(gmallCorrelationData.getId(),
                    JSON.toJSONString(gmallCorrelationData),MqConst.OBJECT_TIMEOUT, TimeUnit.MINUTES);
            //睡一会
            try {
                Thread.sleep(3000);
                //发消息
                rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),
                        gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),
                        gmallCorrelationData);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            //打印日志记录下来
            log.error("重试次数已用尽：" + JSON.toJSONString(gmallCorrelationData));
        }
    }


    //队列应答的回调方法   下面的方法只有在队列接收消息失败的时候 才会执行
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // 反序列化对象输出
        System.out.println("消息主体: " + new String(message.getBody()));
        System.out.println("应答码: " + replyCode);//314
        System.out.println("描述：" + replyText);//no_routing
        System.out.println("消息使用的交换器 exchange : " + exchange);
        System.out.println("消息使用的路由键 routing : " + routingKey);

        //是否选择重新发送  交换机  路由关键词  消息体  次数
        String correlationDataId = (String) message.
                getMessageProperties().getHeaders().get("spring_returned_message_correlation");
        //发送正常消息 如果出现队列接收不到消息 队列应答回调 correlationDataId 主键
        if(StringUtils.isEmpty(correlationDataId)){
            return;//基于插件的延迟消息造成的队列应答执行回调
        }

        String jsonGmallCorrelationData = (String) redisTemplate.opsForValue().get(correlationDataId);
        if(StringUtils.isEmpty(jsonGmallCorrelationData)){
            return;
        }
        log.error("队列应答失败：重新发送：" + jsonGmallCorrelationData);
        GmallCorrelationData gmallCorrelationData =
                JSON.parseObject(jsonGmallCorrelationData,GmallCorrelationData.class);//从缓存Redis中获取出此对象呢
        //判断当前发送消息是延迟消息
        if(gmallCorrelationData.isDelay()){
            return ;
        }
        //不重新发送
        this.addRetry(gmallCorrelationData);
    }

}